﻿using System;
using System.Collections.Generic;
using System.Html;
using System.Runtime.CompilerServices;
using System.Serialization;
using Nsb.Classes;
using jQueryApi;
using Nsb;

namespace UnitTestHarness
{
    public delegate void ClosureDelegate(object pass);
    public delegate bool AddResultTest();
    public delegate void DoitDelegate();
    public delegate object UltraStringifyDelegate(string n, string v);

    /// <summary>
    /// Client side test harness class.
    /// </summary>
    public partial class TestBase
    {
        public static TestLogCollection TestLogResults;
        public static TestLogResult LastResult;
        public static TestLogResult CurrentResult;

        public static TestCollection Collection = new TestCollection();
        public static Date Never = new Date("1/1/1970");
        public static string NotAssigned = "--NotAssigned--";
        public static Element TestOutputElement = null;
        public static jQueryObject TestListObject = null;
        public static int Interval = 1;
        public static Date CollectionStartTime;
        public static Date CollectionEndTime;

        private static string StorageKey = "testResults";
        private static TestTallies Tallies = new TestTallies();
        private static string TestWrapperClass = "TestingAreaWrapper";
        private static string TestOutputClass = "TestingOutputWrapper";

        // Instance members
        private string _SuiteName = "";

        [AlternateSignature]
        public extern TestBase();

        public TestBase(string title)
        {
            if (Arguments.Length >= 1)
                SuiteName = title;
        }

        public string SuiteName
        {
            get
            {
                return _SuiteName;
            }
            set
            {
                _SuiteName = value;
            }
        }

        #region StaticProperties

        /// <summary>
        /// Gets a unique id as the system timestamp + a unique number from 0 to 9999
        /// </summary>
        public static int IdCnt = 0;
        public static string Id
        {
            get
            {
                string id = (IdCnt++).ToString();
                id = "0000".Substr(id.Length - 1) + id;
                return (new Date()).GetTime().ToString() + id;
            }
        }

        #endregion

        #region StaticMethods

        [PreserveName, PreserveCase]
        public static void RunTests(Element el)
        {
            StartTestCollection(Collection.Suites, el);
        }

        public static TestSuite AddSuite(TestSuite suite)
        {
            Collection.Suites[Collection.Suites.Length] = suite;
            return suite;
        }

        public static TestTest[] AddTests(Test[] tests)
        {
            TestTest[] tt = (TestTest[])new Array();
            foreach (Test test in tests)
            {
                tt[tt.Length] = new TestTest(test);
            }
            return tt;
        }

        /// <summary>
        /// Start a test suite run.
        /// </summary>
        /// <param name="tests">The tests.</param>
        /// <param name="el">container element for results</param>
        internal static void StartTestCollection(TestBase[] tests, Element el)
        {
            TestCollection collection = new TestCollection();
            collection.Suites = (TestSuite[])tests;

            jQuery.FromElement(el).AddClass(TestWrapperClass);
            TestingCss();
            TestOutputElement = el;
            PrintControlBar(el);
            ResetPrintout();
            TestLogResults = LoadResults();

            DoSuites();
        }

        public static string LocalTime(Date date)
        {
            return date.ToString();
        }

        internal static TestLogResult GetResultById(string id)
        {
            foreach (TestLogSuite suite in TestLogResults.suites)
            {
                foreach (TestLogTest test in suite.tests)
                {
                    foreach (KeyValuePair<string, TestLogResult> results in test.results)
                    {
                        if (results.Value.id == id)
                            return results.Value;
                    }
                }
            }
            throw new Exception("Breaking condition - test result with requested id does not exist!");
        }

        [AlternateSignature]
        public static extern void DoSuites();

        private static int DoCount = 0;

        /// <summary>
        /// Do a set of test suites
        /// </summary>
        /// <param name="delay">delay before starting</param>
        public static void DoSuites(int delay)
        {
            DoCount = 0;
            if (Arguments.Length < 1) delay = 0;
            Window.SetTimeout(delegate
            {
                CollectionStartTime = CollectionEndTime = new Date();
                CurrentResult = LastResult = null;
                Test test = Collection.FirstTest();

                DoitDelegate fn2 = null;
                DoitDelegate fn = (DoitDelegate)delegate
                {
                    TestLogSuite[] suitesLog = TestLogResults.suites;
                    if (suitesLog[Collection.SuiteIndex] == null)
                        suitesLog[Collection.SuiteIndex] = new TestLogSuite();
                    TestSuite suite = Collection.Suites[Collection.SuiteIndex];
                    TestTest[] tests = suite.Tests;

                    TestLogTest[] testsLog = TestLogResults.suites[Collection.SuiteIndex].tests;
                    if (testsLog[Collection.TestIndex] == null)
                        testsLog[Collection.TestIndex] = new TestLogTest();

                    if (Collection.SuiteChanged)
                        PrintSuiteName(suite.SuiteName);

                    Test t = tests[Collection.TestIndex].test;
                    t();
                    CollectionEndTime = new Date();
                    if (LastResult != null)
                        PrintResult(LastResult);
                    if (CurrentResult != null)
                        PrintResult(CurrentResult);
                    CurrentResult = LastResult = null;
                    if (Collection.SuiteIndex == Collection.Suites.Length - 1 && Collection.TestIndex == tests.Length - 1)
                    {
                        Tallies.TotalTime = (CollectionEndTime.GetTime() - CollectionStartTime.GetTime());
                        SaveResults();
                    }
                    else
                    {
                        test = Collection.NextTest();
                        if (test != null)
                            Window.SetTimeout(fn2, Interval);
                    }

                };

                fn2 = fn;
                fn();

            }, delay);
        }

        /// <summary>
        /// Loads test run results (currently only from localStorage)
        /// </summary>
        /// <returns></returns>
        public static TestLogCollection LoadResults()
        {
            TestLogCollection collection;
            collection = (TestLogCollection)Script.Eval("(" + GetResultString() + ")");
            RestoreResultMethods(collection);
            return collection;
        }

        /// <summary>
        /// Save test run results (currently only saves to localStorage)
        /// </summary>
        public static void SaveResults()
        {
            PutResultString(UltraStringify(TestLogResults));
        }

        public static string UltraStringify(object ob)
        {
            return Json.Stringify(ob, delegate(string n, object v)
            {
                if (Type.GetScriptType(v) == "function")
                {
                    //Inform.Log("Json.Stringify.fn: {0} = {1}", n, v);
                    return v.ToString();
                }
                return v;
            });
            //return (string)Script.Literal("JSON.stringify({0}, {1})", ob, (UltraStringifyDelegate)UltraStringifyEncode);
        }

        //private static object UltraStringifyEncode(string n, object v)
        //{
        //    if (Type.GetScriptType(v) == "function")
        //    {
        //        Inform.Log("Json.Stringify.fn: {0} = {1}", n, v);
        //        return v.ToString();
        //    }
        //    return v;
        //}

        private static string GetResultString()
        {
            string result = (string)Window.LocalStorage.GetItem(StorageKey);
            result = !String.IsNullOrEmpty(result) ? result : TestLogJsonTemplate;
            return result;
        }

        private static void PutResultString(string txt)
        {
            Window.LocalStorage.SetItem(StorageKey, Script.IsValue(txt) ? txt : TestLogJsonTemplate);
        }


        /// <summary>
        /// Restores the result methods to json loaded objects
        /// by creating a new object and copying properties.
        /// </summary>
        /// <param name="collection">The collection.</param>
        internal static void RestoreResultMethods(TestLogCollection collection)
        {
            if (!Script.IsNullOrUndefined(collection.suites)) foreach (TestLogSuite suite in collection.suites)
            {
                foreach (TestLogTest test in suite.tests)
                {
                    foreach (KeyValuePair<string, TestLogResult> results in test.results)
                    {
                        TestLogResult result1 = results.Value;
                        TestLogResult result2 = new TestLogResult();
                        jQuery.Extend(result2, result1);
                        test.results[results.Key] = result2;
                    }
                }
            }
        }

        #endregion

        #region InstanceMethods

        private string GetTestName()
        {
            RegularExpression rxName = new RegularExpression(@"function\s+([^\s\(]+)", "im");
            RegularExpression rxBody = new RegularExpression(@"function\s+([^\s\{]+)", "im");
            Function fn = (Function)Script.Literal("arguments.callee");
            string name = "";
            while (fn != null)
            {
                string[] match = fn.ToString().Match(rxName);
                if (match == null)
                {
                    if (name == "")
                        continue;
                    break;
                }
                name = match[1];
                //Inform.Log("-- Function {0}", name);
                fn = (Function)Script.Literal("{0}.caller", fn);
            }
            return name.ReplaceRegex((RegularExpression)Script.Literal("/[_$]/g"), ".");
        }

        /// <summary>
        /// Verifies result by running a delegate.
        /// </summary>
        /// <param name="name">test name</param>
        /// <param name="fn">delegate returing boolean which indicates pass or failure</param>
        [AlternateSignature]
        public extern TestLogResult Verify(string name, AddResultTest fn);

        /// <summary>
        /// Verifies result with user approvable (no default) value. Approved value is saved for future test runs.
        /// </summary>
        /// <param name="name">test name</param>
        /// <param name="value">value at this moment</param>
        [AlternateSignature]
        public extern TestLogResult Verify(string name, object value);

        /// <summary>
        /// Verifies result with a default expected value.
        /// </summary>
        /// <param name="name">test name</param>
        /// <param name="value">value at this moment</param>
        /// <param name="expectedValue">expected value</param>
        public TestLogResult Verify(string name, object value, object expectedValue)
        {
            if (LastResult != null)
                PrintResult(LastResult);
            string testName = GetTestName();
            string id = Id;
            if (Arguments.Length < 3) expectedValue = NotAssigned;
            //Inform.Log("Arguments.Length = {0}", Arguments.Length);
            name = testName + " :: " + name;
            Inform.Log("{0}) {1}", DoCount++, name);
            Date now = new Date();
            TestLogResult result = TestLogResults.suites[Collection.SuiteIndex].tests[Collection.TestIndex].results[name];
            if (result == null) 
                result = new TestLogResult();
            LastResult = CurrentResult;
            CurrentResult = result;
            TestLogResults.suites[Collection.SuiteIndex].suiteName = SuiteName;
            if (Type.GetScriptType(value) == "function")
            {
                AddResultTest tFn = (AddResultTest)value;
                value = tFn();
                result.validResult = true;
                result.hasDefault = true;
                result.code = tFn.ToString().Match((RegularExpression)Script.Literal(@"/(\{[\s\S]*)/m"))[1];
            }
            else
            {
                if ((string)expectedValue != NotAssigned)
                {
                    result.validResult = expectedValue;
                    result.hasDefault = true;
                }
                else
                    result.hasDefault = false;
            }
            bool passed = result.validResult == value ? true : false;
            result.id = id;
            result.title = SuiteName;
            result.label = name;
            result.lastResult = value;
            result.lastRun = now;
            result.passed = passed;
            result.lastPassed = passed ? now : Never;
            TestLogResults.suites[Collection.SuiteIndex].tests[Collection.TestIndex].results[name] = result;
            return result;
        }

        #endregion

    }

}